/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.properties;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.elsfs.cloud.common.annotations.security.IgnoringAuthentication;
import org.elsfs.cloud.common.util.http.RequestMethod;
import org.elsfs.cloud.common.util.lang.RegExpUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;

/**
 * security 参数
 *
 * @author zeng
 */
@Data
@ConfigurationProperties(prefix = "elsfs.security")
@Slf4j
public class ElsfsSecurityProperties implements BeanFactoryAware {
  /** 忽略认证的请求 */
  public static final Map<RequestMethod, Set<String>> IGNORE_HTTP_REQUEST_METHOD = new HashMap<>();

  private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
  private static final String HANDLER_MAPPING_BEAN_NAME = "requestMappingHandlerMapping";

  static {
    var def =
        Arrays.stream(
                new String[] {
                  "**.html", // swagger-ui/index.html
                  "swagger-ui/**",
                  "**.css",
                  "css/**.css",
                  "**.js",
                  "/templates/css/**",
                  "/webjars/**",
                  "/actuator/**",
                  "/error",
                  "/token/**",
                  "/favicon.ico",
                  "/img/**",
                  "/login",
                  "/error",
                  "actuator/health", // 监控
                  "/sys/randomImage/*",
                  "v3/api-docs/swagger-config",
                  "v3/api-docs"
                })
            .collect(Collectors.toCollection(HashSet::new));
    IGNORE_HTTP_REQUEST_METHOD.put(RequestMethod.GET, def);
  }

  private BeanFactory beanFactory;

  /** 认证服务器的issuer */
  private String issuer;

  /** 认证服务器的授权同意页面 */
  private String consentPage = "/oauth2/consent";

  private String loginPage = "/login";

  /** 跨域配置 */
  @NestedConfigurationProperty private Cors cors = new Cors();

  /** 忽略认证的请求 */
  private Map<RequestMethod, HashSet<String>> ignoreHttpMethod = new HashMap<>();

  /**
   * 获取忽略认证的请求
   *
   * @return Map
   */
  public Map<RequestMethod, HashSet<String>> getIgnoreHttpMethod() {
    afterPropertiesSet();
    initIgnoreLoginPage();
    for (RequestMethod method : RequestMethod.values()) {
      initIgnoreHttpMethodForRequest(method);
    }
    return ignoreHttpMethod;
  }

  private void initIgnoreHttpMethodForRequest(RequestMethod method) {
    HashSet<String> hashSet = ignoreHttpMethod.computeIfAbsent(method, (k) -> new HashSet<>());
    hashSet.addAll(IGNORE_HTTP_REQUEST_METHOD.getOrDefault(method, new HashSet<>()));
  }

  protected void initIgnoreLoginPage() {
    IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(
        RequestMethod.POST, k -> new HashSet<>(List.of(loginPage)));
    IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(
        RequestMethod.GET, k -> new HashSet<>(List.of(loginPage)));
    IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(
        RequestMethod.DELETE, k -> new HashSet<>(List.of(loginPage)));
    IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(
        RequestMethod.PUT, k -> new HashSet<>(List.of(loginPage)));
    IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(
        RequestMethod.POST, k -> new HashSet<>(List.of(loginPage)));
  }

  protected void afterPropertiesSet() {
    servlet();
    reactive();
  }

  private void reactive() {
    var beanProvider =
        beanFactory.getBeanProvider(
            org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping
                .class);
    RequestMappingHandlerMapping mapping = beanProvider.getIfUnique();
    if (mapping == null) {
      LOGGER.warn("RequestMappingHandlerMapping not found");
      return;
    }
    Map<RequestMappingInfo, HandlerMethod> methodMap = mapping.getHandlerMethods();
    for (RequestMappingInfo info : methodMap.keySet()) {
      // 获取方法
      HandlerMethod handlerMethod = methodMap.get(info);
      IgnoringAuthentication annotation =
          AnnotationUtils.findAnnotation(handlerMethod.getMethod(), IgnoringAuthentication.class);
      if (annotation == null) {
        return;
      }
      Set<org.springframework.web.bind.annotation.RequestMethod> requestMethods =
          info.getMethodsCondition().getMethods();
      if (requestMethods.isEmpty()) {
        return;
      }
      Set<RequestMethod> methodSet =
          requestMethods.stream()
              .map(m -> RequestMethod.resolve(m.name()))
              .collect(Collectors.toSet());

      Set<String> directPaths = info.getDirectPaths();
      for (String uri : directPaths) {
        for (RequestMethod requestMethod : methodSet) {
          IGNORE_HTTP_REQUEST_METHOD.computeIfAbsent(requestMethod, k -> new HashSet<>()).add(uri);
        }
      }
    }
  }

  private void servlet() {
    var beanProvider =
        beanFactory.getBeanProvider(
            org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
                .class);
    var mapping = beanProvider.getIfUnique();
    if (mapping == null) {
      LOGGER.warn("RequestMappingHandlerMapping not found");
      return;
    }
    var map = mapping.getHandlerMethods();
    for (var info : map.keySet()) {
      // 获取方法
      var handlerMethod = map.get(info);
      var method =
          AnnotationUtils.findAnnotation(handlerMethod.getMethod(), IgnoringAuthentication.class);
      // 获取请求方式
      var methods = info.getMethodsCondition().getMethods();
      Set<RequestMethod> requestMethods =
          methods.stream().map(m -> RequestMethod.resolve(m.name())).collect(Collectors.toSet());
      if (requestMethods.isEmpty()) {
        return;
      }

      Optional.ofNullable(method)
          .ifPresent(
              ignoringAuthentication ->
                  Objects.requireNonNull(info.getPathPatternsCondition())
                      .getPatternValues()
                      .forEach(
                          patternValue -> {
                            for (RequestMethod requestMethod : requestMethods) {
                              IGNORE_HTTP_REQUEST_METHOD
                                  .computeIfAbsent(requestMethod, k -> new HashSet<>())
                                  .add(patternValue);
                            }
                          }));

      // 获取类上边的注解, 替代path variable 为 *
      var controller =
          AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), IgnoringAuthentication.class);
      Optional.ofNullable(controller)
          .ifPresent(
              inner ->
                  Objects.requireNonNull(info.getPathPatternsCondition())
                      .getPatternValues()
                      .forEach(
                          uri -> {
                            for (RequestMethod requestMethod : requestMethods) {
                              IGNORE_HTTP_REQUEST_METHOD
                                  .computeIfAbsent(requestMethod, k -> new HashSet<>())
                                  .add(RegExpUtils.replaceAll(uri, PATTERN, "*"));
                            }
                          }));
    }
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
  }

  /** Cors */
  @Data
  public static class Cors {
    public static final String ALL = "*";
    private boolean enabled = false;

    /**
     * 跨域请求允许的源 例如：
     *
     * <ul>
     *   <li>特定域，例如 {@code https://domain1.com}
     *   <li>以逗号分隔的特定域列表:{@code https://a1.com,https://a2.com}
     *   <li>CORS为所有原点定义了特殊值{@code *}
     * </ul>
     */
    private List<String> origins = List.of(Cors.ALL);

    /**
     * 替代 origins，它支持更灵活的起源模式，除了端口列表之外，主机名中的任何位置都有“*”。示例：
     *
     * <ul>
     *   <li>https://*.domain1.com--以domain1.com结尾的域
     *   <li>https://*.domain1.com:[8080081]--端口8080或端口8081上以domain1.com结尾的域
     *   <li>https://*.domain1.com:[*]--任何端口上以domain1.com结尾的域，包括默认端口
     *   <li>*
     * </ul>
     */
    private List<String> allowedOriginPatterns = List.of(Cors.ALL);

    /** 允许跨域的请求头 */
    private List<String> allowedHeaders = List.of(Cors.ALL);

    /** 允许跨域的请求方式 */
    private List<String> allowedMethods = List.of(Cors.ALL);
  }
}
